package server

import (
  "bytes"
  "database/sql"
  "errors"
  "fmt"
  "sync"
  "time"
)

const (
  MAX_DEVICE_NUM     = 8
  DEFALUT_MEMBER_NUM = 5
)

var (
  errNodeTooBig = errors.New("nodeTooBig")
)

type NodeType uint8

const (
  NODE_MEMBER NodeType = iota + 1
  NODE_DEVICE
)

// member && family
type hostCache struct {
  user2host map[string]string
  uuid2host map[string]string

  sync.RWMutex
}

var dflHostCache = newHostCache()

func newHostCache() *hostCache {
  return &hostCache{
    user2host: make(map[string]string, 1000),
    uuid2host: make(map[string]string, 1000),
  }
}

func (this *hostCache) getHostByName(username string) string {
  this.RLock()
  name := this.user2host[username]
  this.RUnlock()
  return name
}

func (this *hostCache) getHostByUUID(uuid string) string {
  this.RLock()
  name := this.uuid2host[uuid]
  this.RUnlock()
  return name
}

func (this *hostCache) addHost(hostname string, uuid string, username string) {
  this.Lock()
  this.user2host[username] = hostname
  this.uuid2host[uuid] = hostname
  this.Unlock()
}

type Node struct {
  name, state string
  nodeType    NodeType

  isLogin  bool
  tmAccess int64
  req      *requestContext
}

func (this *Node) On(c *requestContext) {
  if this.nodeType == NODE_MEMBER {
    this.state = "on"
  } else if this.nodeType == NODE_DEVICE {
    // may be other value
    this.state = "on"
  }
  this.isLogin = true
  this.tmAccess = time.Now().Unix()
  this.req = c
}

func (this *Node) Off() {
  if this.nodeType == NODE_MEMBER {
    this.state = "off"
  } else if this.nodeType == NODE_DEVICE {
    // may be other value
    this.state = "off"
  }
  this.isLogin = false
  this.tmAccess = 0
  this.req = nil
}

// family
type Family struct {
  name string
  uuid string

  members map[string]*Node
  devices map[string]*Node

  sync.RWMutex
}

func newFamily(name string) *Family {
  return &Family{
    name:    name,
    members: make(map[string]*Node, DEFALUT_MEMBER_NUM),
    devices: make(map[string]*Node, MAX_DEVICE_NUM),
  }
}

func (this *Family) NewMember(username string) (*Node, bool) {
  this.Lock()
  info, ok := this.members[username]
  if !ok {
    info = &Node{name: username, state: "off", nodeType: NODE_MEMBER}
    this.members[username] = info
    dflHostCache.addHost(this.name, this.uuid, username)
  }
  this.Unlock()
  return info, ok
}

func (this *Family) InsertMember(db *sql.DB, name, pasword string) (*Node, error) {
  this.Lock()
  defer this.Unlock()

  info, ok := this.members[name]
  if ok {
    return info, nil
  }

  strSql := "INSERT INTO `user`(`host`,`hostname`,`userid`,`name`,`password`) VALUES(0,?,?,?,?)"
  _, err := db.Exec(strSql, this.name, this.uuid, name, pasword)
  if err != nil {
    return nil, err
  }
  info = &Node{name: name, state: "off", nodeType: NODE_MEMBER}
  this.members[name] = info

  return info, nil
}

func (this *Family) GetMember(username string) *Node {
  this.RLock()
  info := this.members[username]
  this.RUnlock()
  return info
}

func (this *Family) NewDevice(devname string) (*Node, bool) {
  this.Lock()
  defer this.Unlock()

  if len(this.devices) >= MAX_DEVICE_NUM {
    return nil, false
  }
  info, ok := this.devices[devname]
  if !ok {
    info = &Node{name: devname, state: "off", nodeType: NODE_DEVICE}
    this.devices[devname] = info
  }
  return info, ok
}

func (this *Family) InsertDevice(db *sql.DB, devname string) (*Node, error) {
  this.Lock()
  defer this.Unlock()

  if len(this.devices) >= MAX_DEVICE_NUM {
    return nil, errNodeTooBig
  }
  info, ok := this.devices[devname]
  if ok {
    return info, nil
  }

  strSql := "INSERT INTO `node`(`userid`,`nodeid`,`command`,`value`) VALUES(?,?,'0','0')"
  _, err := db.Exec(strSql, this.uuid, devname)
  if err != nil {
    return nil, err
  }
  info = &Node{name: devname, state: "off", nodeType: NODE_DEVICE}
  this.devices[devname] = info

  return info, nil
}

func (this *Family) GetDevice(devname string) *Node {
  this.RLock()
  info := this.devices[devname]
  this.RUnlock()
  return info
}

func (this *Family) GetAllNodeInfo() []byte {
  this.RLock()
  defer this.RUnlock()

  var msg string
  var buf bytes.Buffer

  for _, info := range this.devices {
    msg = fmt.Sprintf("nodeinfo ok %s %s NULL 0\r\n", info.name, info.state)
    buf.WriteString(msg)
  }
  return buf.Bytes()
}

func (this *Family) GetAllMemberInfo() []byte {
  this.RLock()
  defer this.RUnlock()

  var msg string
  var buf bytes.Buffer

  for _, info := range this.members {
    msg = fmt.Sprintf("userinfo ok %s %s NULL 0\r\n", info.name, info.state)
    buf.WriteString(msg)
  }
  return buf.Bytes()
}

func (this *Family) MemberLoginOut(username string) {
  m := this.GetMember(username)
  if m == nil {
    return
  }
  m.Off()
}

func (this *Family) DeviceLoginOut(devname string) {
  m := this.GetDevice(devname)
  if m == nil {
    return
  }
  m.Off()
}

func (this *Family) SendMsgToAll(msg []byte) {
  this.RLock()
  defer this.RUnlock()

  for _, state := range this.members {
    if state.isLogin && state.req != nil {
      state.req.resp.writeBulk(msg)
      state.req.resp.flush()
    }
  }
}

func (this *Family) SendMsgToOtherMembers(msg []byte, c *Node) {
  this.RLock()
  defer this.RUnlock()

  for _, info := range this.members {
    if info.name == c.name {
      continue
    }
    if info.isLogin && info.req != nil {
      info.req.resp.writeBulk(msg)
      info.req.resp.flush()
    }
  }
}

type FamilyMgr struct {
  families map[string]*Family

  sync.RWMutex
}

var dflFamilyMgr = newFamilyMgr()

func newFamilyMgr() *FamilyMgr {
  return &FamilyMgr{
    families: make(map[string]*Family, 1000),
  }
}

func (this *FamilyMgr) NewFamily(hostname string) (*Family, bool) {
  this.Lock()
  info, ok := this.families[hostname]
  if !ok {
    info = newFamily(hostname)
    this.families[hostname] = info
  }
  this.Unlock()
  return info, ok
}

func (this *FamilyMgr) GetFamilyByHostName(hostname string) *Family {
  this.RLock()
  info := this.families[hostname]
  this.RUnlock()
  return info
}

func (this *FamilyMgr) GetFamilyByNodeName(nodename string) *Family {
  hostname := dflHostCache.getHostByName(nodename)
  if hostname == "" {
    return nil
  }
  return this.GetFamilyByHostName(hostname)
}

func (this *FamilyMgr) GetFamilyByUUID(uuid string) *Family {
  hostname := dflHostCache.getHostByUUID(uuid)
  if hostname == "" {
    return nil
  }
  return this.GetFamilyByHostName(hostname)
}

func (this *FamilyMgr) GetMember(username string) *Node {
  family := this.GetFamilyByNodeName(username)
  if family == nil {
    return nil
  }
  return family.GetMember(username)
}
